package com.folioreader.ui.view;

import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ActionMode;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;


import android.view.ViewGroup;
import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import android.widget.PopupWindow;
import android.widget.Toast;

import androidx.annotation.RequiresApi;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.content.ContextCompat;
import androidx.core.view.GestureDetectorCompat;

import com.folioreader.Config;
import com.folioreader.R;
import com.folioreader.model.DisplayUnit;
import com.folioreader.model.HighLight;
import com.folioreader.model.HighlightImpl;
import com.folioreader.model.sqlite.HighLightTable;
import com.folioreader.ui.activity.FolioActivityCallback;
import com.folioreader.ui.fragment.FolioPageFragment;
import com.folioreader.util.AppUtilNew;
import com.folioreader.util.HighlightUtil;
import com.folioreader.util.UiUtil;

import org.json.JSONException;
import org.json.JSONObject;

public class FolioWebViewNew extends WebView {
    private static final String TAG = "FolioWebViewNew";
    private static final int IS_SCROLLING_CHECK_TIMER = 100;
    private static final int IS_SCROLLING_CHECK_MAX_DURATION = 10000;

    private int horizontalPageCount = 0;
    private DisplayMetrics displayMetrics = null;
    private float density = 0f;
    private ScrollListener mScrollListener = null;
    private SeekBarListener mSeekBarListener = null;
    private GestureDetectorCompat gestureDetectorCompat;
    private MotionEvent eventActionDown = null;
    private int pageWidthCssDp = 0;
    private float pageWidthCssPixels = 0f;
    private WebViewPagerNew webViewPager;
    private Handler uiHandler;
    private FolioActivityCallback folioActivityCallback;
    private FolioPageFragment pageFragment;

    private ActionMode actionMode = null;
    private TextSelectionCb textSelectionCb = null;
    private TextSelectionCb2 textSelectionCb2 = null;
    private Rect selectionRect = new Rect();
    private Rect popupRect = new Rect();
    private PopupWindow popupWindow = new PopupWindow();
    private View viewTextSelection;
    private int isScrollingCheckDuration = 0;
    private Runnable isScrollingRunnable = null;
    private int oldScrollX = 0;
    private int oldScrollY = 0;
    private int lastTouchAction = 0;
    private boolean destroyed = false;
    private int handleHeigth = 0;
    private LastScrollType lastScrollType = null;


    int contentHeightVal = (int) Math.floor(getContentHeight() * getScale());

    public int getContentHeightVal() {
        return contentHeightVal;
    }

    int webViewHeight = getMeasuredHeight();

    public int getWebViewHeight() {
        return webViewHeight;
    }

    private enum LastScrollType {
        USER, PROGRAMMATIC
    }

    public interface ScrollListener {
        void onScrollChange(int percent);
    }

    public interface SeekBarListener {
        void fadeInSeekBarIfInvisible();
    }


    public static boolean onWebViewConsoleMessage(ConsoleMessage cm, String LOG_TAG, String msg) {
        Log.e(TAG, "onWebViewConsoleMessage: " + msg);
        return false;
    }

    @JavascriptInterface
    public String getDirection() {
        return folioActivityCallback.getDirection().toString();
    }

    @JavascriptInterface
    public int getTopDistraction(String unitString) {
        DisplayUnit unit = DisplayUnit.valueOf(unitString);
        return folioActivityCallback.getTopDistraction(unit);
    }

    @JavascriptInterface
    public int getBottomDistraction(String unitString) {
        DisplayUnit unit = DisplayUnit.valueOf(unitString);
        return folioActivityCallback.getBottomDistraction(unit);
    }

    @JavascriptInterface
    public String getViewportRect(String unitString) {
        DisplayUnit unit = DisplayUnit.valueOf(unitString);
        Rect rect = folioActivityCallback.getViewportRect(unit);
        return UiUtil.rectToDOMRectJson(rect);
    }

    @JavascriptInterface
    public void toggleSystemUI() {
        uiHandler.post(new Runnable() {
            @Override
            public void run() {
                folioActivityCallback.toggleSystemUI();
            }
        });
    }

    @JavascriptInterface
    public boolean isPopupShowing() {
        return popupWindow.isShowing();
    }

    @JavascriptInterface
    public boolean dismissPopupWindow() {
        boolean wasShowing = popupWindow.isShowing();
        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
            popupWindow.dismiss();
        } else {
            uiHandler.post(new Runnable() {
                @Override
                public void run() {
                    popupWindow.dismiss();
                }
            });
        }
        selectionRect = new Rect();
        uiHandler.removeCallbacks(isScrollingRunnable);
        isScrollingCheckDuration = 0;
        return wasShowing;
    }

    @JavascriptInterface
    public void onTextSelectionItemClicked(int id, String selectedText) {
        uiHandler.post(new Runnable() {
            @Override
            public void run() {
                evaluateJavascript("javascript:clearSelection()", null);
            }
        });

        if (id == R.id.copySelection) {
            UiUtil.copyToClipboard(getContext(), selectedText);
            Toast.makeText(getContext(), getContext().getString(R.string.copied), Toast.LENGTH_SHORT).show();
        } else if (id == R.id.shareSelection) {
            UiUtil.share(getContext(), selectedText);
        } else {
            Log.w(TAG, "-> onTextSelectionItemClicked -> unknown id = " + id);
        }
    }

    @JavascriptInterface
    public void deleteThisHighlight(String id) {
        if (id.isEmpty()) {
            return;
        }
        HighlightImpl highlightImpl = HighLightTable.getHighlightForRangy(id);
        if (HighLightTable.deleteHighlight(id)) {
            final String rangy = HighlightUtil.generateRangyString(pageFragment.getPageName());
            uiHandler.post(new Runnable() {
                @Override
                public void run() {
                    pageFragment.loadRangy(rangy);
                }
            });

            if (highlightImpl != null) {
                HighlightUtil.sendHighlightBroadcastEvent(getContext(), highlightImpl, HighLight.HighLightAction.DELETE);
            }
        }
    }

    private void onHighlightColorItemsClicked(HighlightImpl.HighlightStyle style, boolean isAlreadyCreated) {
        pageFragment.highlight(style, isAlreadyCreated);
        dismissPopupWindow();
    }

    private class HorizontalGestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            lastScrollType = LastScrollType.USER;
            return false;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (!webViewPager.isScrolling()) {
                uiHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        scrollTo(getScrollXPixelsForPage(webViewPager.getCurrentItem()), 0);
                    }
                }, 100);

            }
            return true;
        }

        @Override
        public boolean onDown(MotionEvent e) {
            eventActionDown = MotionEvent.obtain(e);
            FolioWebViewNew.super.onTouchEvent(e);
            return true;
        }
    }

    @Override
    public void destroy() {
        super.destroy();
        dismissPopupWindow();
        destroyed = true;
    }

    private class VerticalGestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            lastScrollType = LastScrollType.USER;
            return false;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            lastScrollType = LastScrollType.USER;
            return false;
        }
    }

    public FolioWebViewNew(Context context) {
        super(context);
    }

    public FolioWebViewNew(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FolioWebViewNew(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private void init() {
        uiHandler = new Handler();
        displayMetrics = getResources().getDisplayMetrics();
        density = displayMetrics.density;
        gestureDetectorCompat = null;
        if (folioActivityCallback.getDirection() == Config.Direction.HORIZONTAL) {
            gestureDetectorCompat = new GestureDetectorCompat(getContext(), new HorizontalGestureListener());
        } else {
            gestureDetectorCompat = new GestureDetectorCompat(getContext(), new VerticalGestureListener());
        }
        initViewTextSelection();
    }

    public void initViewTextSelection() {
        Drawable textDelectionMiddleDrawable = ContextCompat.getDrawable(getContext(), R.drawable.abc_text_select_handle_middle_mtrl_dark);
        handleHeigth = textDelectionMiddleDrawable != null ? textDelectionMiddleDrawable.getIntrinsicHeight() : (int) (24 * density);
        Config config = AppUtilNew.getSavedConfig(getContext());
        ContextThemeWrapper ctw = null;
        if (config != null && config.isNightMode()) {
            ctw = new ContextThemeWrapper(getContext(), R.style.FolioNightTheme);
        } else {
            ctw = new ContextThemeWrapper(getContext(), R.style.FolioDayTheme);
        }
        viewTextSelection = LayoutInflater.from(ctw).inflate(R.layout.text_selection, null);
        viewTextSelection.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

        viewTextSelection.findViewById(R.id.yellowHighlight).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onHighlightColorItemsClicked(HighlightImpl.HighlightStyle.Yellow, false);
            }
        });
        viewTextSelection.findViewById(R.id.greenHighlight).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onHighlightColorItemsClicked(HighlightImpl.HighlightStyle.Green, false);
            }
        });
        viewTextSelection.findViewById(R.id.blueHighlight).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onHighlightColorItemsClicked(HighlightImpl.HighlightStyle.Blue, false);
            }
        });
        viewTextSelection.findViewById(R.id.pinkHighlight).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onHighlightColorItemsClicked(HighlightImpl.HighlightStyle.Pink, false);
            }
        });
        viewTextSelection.findViewById(R.id.underlineHighlight).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onHighlightColorItemsClicked(HighlightImpl.HighlightStyle.Underline, false);
            }
        });

        viewTextSelection.findViewById(R.id.deleteHighlight).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                dismissPopupWindow();
                evaluateJavascript("javascript:clearSelection()", null);
                evaluateJavascript("javascript:deleteThisHighlight()", null);
            }
        });
        viewTextSelection.findViewById(R.id.copySelection).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                dismissPopupWindow();
                evaluateJavascript("javascript:onTextSelectionItemClicked(${it.id})", null);
            }
        });
        viewTextSelection.findViewById(R.id.shareSelection).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                dismissPopupWindow();
                evaluateJavascript("javascript:onTextSelectionItemClicked(${it.id})", null);
            }
        });
    }

    public void setParentFragment(FolioPageFragment parentFragment) {
        this.pageFragment = parentFragment;
    }

    public void setFolioActivityCallback(FolioActivityCallback folioActivityCallback) {
        this.folioActivityCallback = folioActivityCallback;
        init();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        pageWidthCssDp = (int) Math.ceil(getMeasuredWidth() / density);
        pageWidthCssPixels = pageWidthCssDp * density;
    }

    public void setScrollListener(ScrollListener mScrollListener) {
        this.mScrollListener = mScrollListener;
    }

    public void setSeekBarListener(SeekBarListener mSeekBarListener) {
        this.mSeekBarListener = mSeekBarListener;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (event == null) {
            return false;
        }
        lastTouchAction = event.getAction();
        Log.e(TAG, "onTouchEvent: " + lastTouchAction);
        boolean flag;
        if (folioActivityCallback.getDirection() == Config.Direction.HORIZONTAL) {
            flag = computeHorizontalScroll(event);
        } else {
            flag = computeVerticalScroll(event);
        }
        return flag;
    }

    private boolean computeVerticalScroll(MotionEvent event) {
        gestureDetectorCompat.onTouchEvent(event);
        return super.onTouchEvent(event);
    }

    private boolean computeHorizontalScroll(MotionEvent event) {
        if (webViewPager == null) {
            return super.onTouchEvent(event);
        }
        webViewPager.dispatchTouchEvent(event);
        boolean gestureReturn = gestureDetectorCompat.onTouchEvent(event);
        return gestureReturn ? true : super.onTouchEvent(event);
    }

    public int getScrollXPixelsForPage(int page) {
        return (int) Math.ceil(page * pageWidthCssPixels);
    }

    public void setHorizontalPageCount(int horizontalPageCount1) {
        this.horizontalPageCount = horizontalPageCount1;
        uiHandler.post(new Runnable() {
            @Override
            public void run() {
                webViewPager = ((View) getParent()).findViewById(R.id.webViewPager);
                webViewPager.setHorizontalPageCount(horizontalPageCount);
            }
        });
    }

    @Override
    public void scrollTo(int x, int y) {
        super.scrollTo(x, y);
        lastScrollType = LastScrollType.PROGRAMMATIC;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        if (mScrollListener != null) {
            mScrollListener.onScrollChange(t);
        }
        super.onScrollChanged(l, t, oldl, oldt);
        if (lastScrollType == LastScrollType.USER) {
            //Log.d(LOG_TAG, "-> onScrollChanged -> scroll initiated by user");
            pageFragment.setSearchLocatorVisible(null);
        }

        lastScrollType = null;
    }

    private class TextSelectionCb implements ActionMode.Callback {

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            evaluateJavascript("javascript:getSelectionRect()", new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String value) {
                    try {
                        JSONObject rectJson = new JSONObject(value);
                        setSelectionRect(
                                rectJson.getInt("left"), rectJson.getInt("top"),
                                rectJson.getInt("right"), rectJson.getInt("bottom")
                        );
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            });
            return false;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            return false;
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            dismissPopupWindow();
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private class TextSelectionCb2 extends ActionMode.Callback2 {

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            menu.clear();
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            return false;
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            dismissPopupWindow();

        }

        @Override
        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
            evaluateJavascript("javascript:getSelectionRect()", new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String value) {
                    try {
                        JSONObject rectJson = new JSONObject(value);
                        setSelectionRect(
                                rectJson.getInt("left"), rectJson.getInt("top"),
                                rectJson.getInt("right"), rectJson.getInt("bottom")
                        );
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public ActionMode startActionMode(ActionMode.Callback callback, int type) {
        textSelectionCb2 = new TextSelectionCb2();
        actionMode = super.startActionMode(textSelectionCb2, type);
        actionMode.finish();
        return actionMode;
    }

    @Override
    public ActionMode startActionMode(ActionMode.Callback callback) {
        textSelectionCb = new TextSelectionCb();
        actionMode = super.startActionMode(textSelectionCb);
        actionMode.finish();
        return actionMode;
    }

    @JavascriptInterface
    public void setSelectionRect(int left, int top, int right, int bottom) {
        Rect currentSelectionRct = new Rect();
        currentSelectionRct.left = (int) (left * density);
        currentSelectionRct.top = (int) (top * density);
        currentSelectionRct.right = (int) (right * density);
        currentSelectionRct.bottom = (int) (bottom * density);

        computeTextSelectionRect(currentSelectionRct);
        uiHandler.post(new Runnable() {
            @Override
            public void run() {
                showTextSelectionPopup();
            }
        });
    }

    private void computeTextSelectionRect(Rect currentSelectionRct) {
        Log.v(TAG, "-> computeTextSelectionRect");

        Rect viewportRect = folioActivityCallback.getViewportRect(DisplayUnit.PX);
        Log.d(TAG, "-> viewportRect -> " + viewportRect);
        if (!Rect.intersects(viewportRect, currentSelectionRct)) {
            uiHandler.post(new Runnable() {
                @Override
                public void run() {
                    popupWindow.dismiss();
                    uiHandler.removeCallbacks(isScrollingRunnable);
                }
            });
            return;
        }
        if (selectionRect.equals(currentSelectionRct)) {
            return;
        }

        selectionRect = currentSelectionRct;
        Rect abovSelectionRect = new Rect(viewportRect);
        abovSelectionRect.bottom = (int) (selectionRect.top - (8 * density));
        Rect belowSelectionRect = new Rect(viewportRect);
        belowSelectionRect.top = selectionRect.bottom + handleHeigth;

        popupRect.left = viewportRect.left;
        popupRect.top = belowSelectionRect.top;
        popupRect.right = popupRect.left + viewTextSelection.getMeasuredWidth();
        popupRect.bottom = popupRect.top + viewTextSelection.getMeasuredHeight();

        int popupY;
        if (belowSelectionRect.contains(popupRect)) {
            Log.i(TAG, "-> show below");
            popupY = belowSelectionRect.top;
        } else {
            popupRect.top = abovSelectionRect.top;
            popupRect.bottom = popupRect.top + viewTextSelection.getMeasuredHeight();

            if (abovSelectionRect.contains(popupRect)) {
                Log.i(TAG, "-> show above");
                popupY = abovSelectionRect.bottom - popupRect.height();
            } else {
                Log.i(TAG, "-> show in middle");
                int popupYDiff = (viewTextSelection.getMeasuredHeight() - selectionRect.height()) / 2;
                popupY = selectionRect.top - popupYDiff;
            }

        }

        int popupXDiff = (viewTextSelection.getMeasuredWidth() - selectionRect.width()) / 2;
        int popupX = selectionRect.left - popupXDiff;
        popupRect.offsetTo(popupX, popupY);

        if (popupRect.left < viewportRect.left) {
            popupRect.right += 0 - popupRect.left;
            popupRect.left = 0;
        }
        if (popupRect.right > viewportRect.right) {
            int dx = popupRect.right - viewportRect.right;
            popupRect.left -= dx;
            popupRect.right -= dx;
        }
    }

    private void showTextSelectionPopup() {

        Log.v(TAG, "-> showTextSelectionPopup");
        Log.d(TAG, "-> showTextSelectionPopup -> To be laid out popupRect -> " + popupRect);

        popupWindow.dismiss();
        oldScrollX = getScrollX();
        oldScrollY = getScrollY();

        isScrollingRunnable = new Runnable() {
            @Override
            public void run() {
                uiHandler.removeCallbacks(isScrollingRunnable);
                int currentScrollX = getScrollX();
                int currentScrollY = getScrollY();
                boolean inTouchMode = lastTouchAction == MotionEvent.ACTION_DOWN || lastTouchAction == MotionEvent.ACTION_MOVE;
                Log.e(TAG, "inTouchModel: " + lastTouchAction + " " + MotionEvent.ACTION_DOWN + " " + MotionEvent.ACTION_MOVE + MotionEvent.ACTION_UP);
                boolean flag = oldScrollX == currentScrollX && oldScrollY == currentScrollY && !inTouchMode;
                Log.e(TAG, "showPopupWindow: oldScrollX:" + oldScrollX + " currentScrollX:" + currentScrollX + " oldScrollY:" + oldScrollY + " currentScrollY:" + currentScrollY + " inTouchMode:" + inTouchMode);
                if (flag) {
                    Log.e(TAG, "-> Stopped scrolling, show Popup");
                    popupWindow.dismiss();
                    popupWindow = new PopupWindow(viewTextSelection, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                    popupWindow.setClippingEnabled(false);
                    popupWindow.showAtLocation(FolioWebViewNew.this, Gravity.NO_GRAVITY, popupRect.left, popupRect.top);

                } else {
                    Log.e(TAG, "-> Still scrolling, don't show Popup");
                    oldScrollX = currentScrollX;
                    oldScrollY = currentScrollY;
                    isScrollingCheckDuration += IS_SCROLLING_CHECK_TIMER;
                    if (isScrollingCheckDuration < IS_SCROLLING_CHECK_MAX_DURATION && !destroyed) {
                        uiHandler.postDelayed(isScrollingRunnable, IS_SCROLLING_CHECK_TIMER);
                    }
                }
            }
        };

        uiHandler.removeCallbacks(isScrollingRunnable);
        isScrollingCheckDuration = 0;
        if (!destroyed)
            uiHandler.postDelayed(isScrollingRunnable, IS_SCROLLING_CHECK_TIMER);
    }

}
